15376
5145
Python中yield关键字的用途是什么?
例如,我试图理解此代码1:
def _get_child_candidates(自身,距离,min_dist,max_dist):
如果self._leftchild和距离-max_dist  = self._median:
产生self._rightchild
这是呼叫者:
结果,候选人= [],[自身]
而候选人:
节点=候选人.pop()
距离= node._get_dist(obj)
如果距离<= max_dist和距离> = min_dist:
result.extend(node._values)
候选人扩展(node._get_child_candidates(距离,min_dist,max_dist))
返回结果
调用_get_child_candidates方法时会发生什么?
是否返回列表?一个元素?再叫一次吗?后续通话什么时候停止?
1.这段代码是由Jochen Schulz(jrschulz)编写的,Jochen Schulz是一个很好的用于度量空间的Python库。这是完整源代码的链接:模块mspace。 
1个
2
下一个
要了解产量是多少,您必须了解什么是发电机。而且,在您了解生成器之前,您必须了解可迭代。
可迭代
创建列表时,可以一一阅读它的项目。逐一读取其项称为迭代:
>>> mylist = [1、2、3]
>>>对于我在我的列表中:
...打印(i)
1个
2
3
mylist是可迭代的。当您使用列表推导时,您将创建一个列表,因此是可迭代的:
>>> mylist = [x * x for x in range(3)]
>>>对于我在我的列表中:
...打印(i)
1个
4
您可以在“ for ... in ...”上使用的所有内容都是可迭代的;列表,字符串,文件...
这些可迭代的方法很方便,因为您可以根据需要读取它们,但是您将所有值存储在内存中,当拥有很多值时,这并不总是想要的。
发电机
生成器是迭代器,一种只能迭代一次的可迭代器。生成器未将所有值存储在内存中,而是即时生成值:
>>> mygenerator =(x * x表示范围(3)中的x)
>>>对于mygenerator中的我:
...打印(i)
1个
4
除了使用()而不是[]之外,其他功能相同。但是,您不能第二次在mygenerator中执行i,因为生成器只能使用一次:它们先计算0,然后忘记它,然后计算1,最后一次计算4。
产量
yield是一个像return一样使用的关键字,只是函数将返回一个生成器。
>>> def createGenerator():
...我的清单=范围(3)
...对于我在我的清单中:
...产生i * i
...
>>> mygenerator = createGenerator()#创建一个生成器
>>> print(mygenerator)#mygenerator是一个对象!
<生成器对象createGenerator位于0xb7555c34>
>>>对于mygenerator中的i:
...打印(i)
1个
4
这是一个无用的示例,但是当您知道函数将返回仅需读取一次的大量值时,它就很方便。
要掌握yield,您必须了解在调用函数时,在函数主体中编写的代码不会运行。该函数仅返回生成器对象,这有点棘手:-)
然后,您的代码将在每次使用生成器时都从停止的地方继续。
现在最困难的部分是:
for第一次调用从您的函数创建的生成器对象时,它将从头开始运行函数中的代码,直到达到yield为止,然后它将返回循环的第一个值。然后,每个后续调用将运行您在函数中编写的循环的另一次迭代,并返回下一个值。这将一直持续到生成器被认为是空的为止,这种情况会在函数运行而没有达到产量时发生。那可能是因为循环已经结束,或者是因为您不再满足“ if / else”的要求。
您的代码说明
发电机:
#在这里创建节点对象的方法,该方法将返回生成器
def _get_child_candidates(自身,距离,min_dist,max_dist):
#这是每次使用generator对象时将被调用的代码:
#如果节点对象的左边还有一个子对象
#AND如果距离合适,则返回下一个孩子
如果self._leftchild和距离-max_dist  = self._median:
产生self._rightchild
#如果函数到达此处,生成器将被视为空
#不超过两个值:左子代和右子代
呼叫者:
#创建一个空列表和一个包含当前对象引用的列表
结果,候选人= list(),[个体]
#循环候选(它们在开始时只包含一个元素)
而候选人:
#获取最后一个候选人并将其从列表中删除
节点=候选人.pop()
#获取obj和候选对象之间的距离
距离= node._get_dist(obj)
#如果距离合适,则可以填写结果
如果距离<= max_dist和距离> = min_dist:
result.extend(node._values)
#在候选人列表中添加候选人的孩子
#因此循环将一直运行,直到看起来为止
#在候选人的子女等的所有子女中
候选人扩展(node._get_child_candidates(距离,min_dist,max_dist))
返回结果
此代码包含几个智能部分:
循环在列表上迭代,但是循环在迭代时列表会扩展:-)这是浏览所有这些嵌套数据的一种简洁方法,即使这样做有点危险,因为您可能会遇到无限循环。在这种情况下,candidates.extend(node._get_child_candidates(distance,min_dist,max_dist))耗尽了生成器的所有值,但是由于创建的生成器对象与之前的值不同,因此会不断创建新的生成器对象节点。
extend()方法是一个列表对象方法,该方法需要可迭代并将其值添加到列表中。
通常我们将一个列表传递给它:
>>>一个= [1、2]
>>> b = [3,4]
>>> a.extend(b)
>>>打印(a)
[1、2、3、4]
但是在您的代码中,它得到了一个生成器,这很好,因为:
您无需两次读取值。
您可能有很多孩子,并且您不希望所有孩子都存储在内存中。
它之所以有效,是因为Python不在乎方法的参数是否为列表。 Python期望可迭代,因此它将与字符串,列表,元组和生成器一起使用!这就是所谓的鸭子输入,这是Python如此酷的原因之一。但这是另一个故事,还有另一个问题...
您可以在这里停止,或者阅读一点以了解生成器的高级用法:
控制发电机耗尽
>>> class Bank():#让我们创建一个银行,建立ATM
...危机=错误
... def create_atm(self):
...虽然不是自我危机:
...产生“ $ 100”
>>> hsbc = Bank()#一切正常时,ATM可以为您提供所需的一切
>>> corner_street_atm = hsbc.create_atm()
>>>打印(corner_street_atm.next())
$ 100
>>>打印(corner_street_atm.next())
$ 100
>>>打印([[corner_street_atm.next()表示范围(5)中的现金])
['$ 100','$ 100','$ 100','$ 100','$ 100']
>>> hsbc.crisis = True#危机来了,没有钱了!
>>>打印(corner_street_atm.next())
<类型'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm()#对于新的ATM甚至如此
>>>打印(wall_street_atm.next())
<类型'exceptions.StopIteration'>
>>> hsbc.crisis = False#问题是,即使在危机后,ATM仍然为空
>>>打印(corner_street_atm.next())
<类型'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm()#建立一个新的以恢复营业
>>>兑换brand_new_atm:
...打印现金
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
...
注意:对于Python 3,请使用print(corner_street_atm .__ next __())或print(next(corner_street_atm))
对于诸如控制对资源的访问之类的各种事情,它可能很有用。
Itertools,您最好的朋友
itertools模块包含用于操纵可迭代对象的特殊功能。曾经希望复制一个发电机吗?
连锁两个发电机?用一个班轮将值嵌套在嵌套列表中? Map / Zip而不创建另一个列表?
然后只需导入itertools。
一个例子?让我们看一下四马比赛的可能到达顺序:
>>>马= [1、2、3、4]
>>>种族= itertools.permutations(马)
>>>打印(种族)
<位于0xb754f1dc的itertools.permutations对象>
>>>打印(列表(itertools.permutations(马)))
[(1、2、3、4),
(1,2,4,3),
(1、3、2、4),
(1、3、4、2),
(1、4、2、3),
(1、4、3、2),
(2,1,3,4),
(2,1,4,3),
(2,3,1,4),
(2、3、4、1),
(2,4,1,3),
(2,4,3,1),
(3,1,2,4),
(3,1,4,2),
(3,2,1,4),
(3,2,4,1),
(3,4,1,2),
(3,4,2,1),
(4,1,2,3),
(4,1,3,2),
(4,2,1,3),
(4,2,3,1),
(4、3、1、2),
(4、3、2、1)]
了解迭代的内部机制
迭代是一个包含可迭代对象(实现__iter __()方法)和迭代器(实现__next __()方法)的过程。
可迭代对象是可以从中获取迭代器的任何对象。迭代器是使您可以迭代可迭代对象的对象。
本文中有更多关于for循环如何工作的内容。
|
了解产量的捷径
当您看到带有yield语句的函数时,请应用以下简单技巧,以了解将发生的情况:
在函数的开头插入一行结果= []。
将每个yield expr替换为result.append(expr)。
在函数的底部插入行返回结果。
是的-没有更多的收益声明!阅读并找出代码。
将功能与原始定义进行比较。
这个技巧可以使您了解函数背后的逻辑,但是使用yield实际发生的事情与基于列表的方法发生的事情显着不同。在许多情况下,yield方法也将具有更高的内存效率和更快的速度。在其他情况下,即使原始函数运行正常,此技巧也会使您陷入无限循环。请继续阅读以了解更多信息...
不要混淆您的Iterable,Iterators和Generators
首先,迭代器协议-当您编写时
对于mylist中的x:
圈体
Python执行以下两个步骤:
获取mylist的迭代器:
调用iter(mylist)->这将返回带有next()方法的对象(或Python 3中的__next __())。
[这是大多数人忘记告诉您的步骤]
使用迭代器遍历项目:
继续在步骤1返回的迭代器上调用next()方法。将next()的返回值分配给x,并执行循环体。如果从next()内部引发异常StopIteration,则意味着迭代器中没有更多值,并且退出了循环。
事实是Python随时想要遍历对象的内容时都执行上述两个步骤-因此它可能是for循环,但也可能是诸如otherlist.extend(mylist)之类的代码(其中otherlist是Python列表) 。
这里的mylist是可迭代的,因为它实现了迭代器协议。在用户定义的类中,可以实现__iter __()方法以使您的类的实例可迭代。此方法应返回迭代器。迭代器是具有next()方法的对象。可以在同一个类上同时实现__iter __()和next(),并使__iter __()返回self。这将在简单情况下起作用,但是当您希望两个迭代器同时在同一个对象上循环时则无效。
这就是迭代器协议,许多对象都实现了该协议:
内置列表,字典,元组,集合,文件。
用户定义的实现__iter __()的类。
发电机。
请注意,for循环不知道它要处理的是哪种对象-它仅遵循迭代器协议,并且很高兴在调用next()时逐项获取对象。内置列表一一返回它们的项,字典一一返回键,文件一一返回行,等等。生成器返回...产生收益的地方:
def f123():
产量1
产量2
产量3
对于f123()中的项目:
打印项目
如果您在f123()中有三个return语句,则不执行yield语句,而只有第一个会执行,该函数将退出。但是f123()不是普通函数。调用f123()时,它不会返回yield语句中的任何值!它返回一个生成器对象。另外,该函数并没有真正退出-进入了挂起状态。当for循环尝试循环生成器对象时,该函数从先前返回的yield之后的下一行开始从其挂起状态恢复,执行下一行代码(在本例中为yield语句),并返回作为下一项。这会一直发生,直到函数退出,此时生成器将引发StopIteration,然后循环退出。
因此,生成器对象有点像适配器-一端通过公开__iter __()和next()方法来保持for循环快乐,从而展示了迭代器协议。但是,在另一端,它仅运行该函数以从中获取下一个值,然后将其放回暂停模式。
为什么使用发电机?
通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。并非在所有情况下都可行,例如如果您有无限循环,或者当列表很长时,它可能会导致内存使用效率低下。另一种方法是实现一个新的可迭代类SomethingIter,该类将状态保留在实例成员中,并在next()(或Python 3中的__next __())方法中执行下一个逻辑步骤。根据逻辑的不同,next()方法内部的代码可能看起来非常复杂,并且容易出现错误。在这里,发电机提供了一种干净而简单的解决方案。
|
这样想:
迭代器只是一个具有next()方法的对象的美化名词。因此,由yield产生的函数最终是这样的:
原始版本:
def some_function():
对于我在xrange(4)中:
让我
对于我在some_function()中:
打印我
这基本上就是Python解释器对上面的代码所做的:
分类:
def __init __():
#从-1开始,以便在下面加1时得到0。
self.count = -1
#__iter__方法将由“ for”循环调用一次。
#其余的魔术发生在此方法返回的对象上。
#在这种情况下,它是对象本身。
def __iter __(自己):
返回自我
#下一个方法将被“ for”循环重复调用
#直到引发StopIteration。
def next(self):
self.count + = 1
如果self.count <4:
返回self.count
其他:
#引发StopIteration异常
#表示迭代器已完成。
#被'for'循环隐式捕获。
提高StopIteration
def some_func():
把它返还()
对于我在some_func()中:
打印我
要了解幕后发生的事情,可以将for循环重写为:
迭代器= some_func()
尝试:
而1:
打印iterator.next()
StopIteration除外:
通过
这样做更有意义还是会让您更加困惑? :)
我应该指出,出于说明目的,这过于简单了。 :)
|
yield关键字简化为两个简单事实:
如果编译器在函数内部的任何位置检测到yield关键字,则该函数不再通过return语句返回。相反,它立即返回一个懒惰的“待处理列表”对象,称为生成器
生成器是可迭代的。什么是迭代?就像列表,集合,范围或字典视图一样,它具有用于以特定顺序访问每个元素的内置协议。
简而言之:生成器是一个懒惰的,增量待定的列表,并且yield语句允许您使用函数符号来编程生成器应逐渐吐出的列表值。
generator = myYieldingFunction(...)
x =列表(发电机)
发电机
v
[x [0],...,???]
发电机
v
[x [0],x [1],...,???]
发电机
v
[x [0],x [1],x [2],...,???]
StopIteration例外
[x [0],x [1],x [2]]完成
列表== [x [0],x [1],x [2]]
例
让我们定义一个函数makeRange,就像Python的range一样。调用makeRange(n)返回一个生成器:
def makeRange(n):
#返回0,1,2,...,n-1
我= 0
而我>> makeRange(5)
<生成器对象makeRange位于0x19e4aa0>
要强制生成器立即返回其未决值,可以将其传递到list()中(就像您可以进行任何迭代一样):
>>>列表(makeRange(5))
[0,1,2,3,4]
将示例与“仅返回列表”进行比较
可以将上面的示例视为仅创建一个列表,并将其附加并返回:
#list-version##generator-version
def makeRange(n):#def makeRange(n):
“”“返回[0,1,2,...,n-1]”“”#〜“”“返回0,1,2,...,n-1”“”
TO_RETURN = []#>
i = 0#i = 0
当我
>>> makeRange(5)
[0,1,2,3,4]
但是,有一个主要区别。请参阅最后一节。
您如何使用发电机
可迭代是列表理解的最后一部分,并且所有生成器都是可迭代的,因此经常像这样使用它们:
#_ITERABLE_
>>> [x + 10 for makeRange(5)中的x]
[10、11、12、13、14]
为了更好地了解生成器,可以使用itertools模块(一定要使用chain.from_iterable,而不要在需要时使用chain)。例如,您甚至可以使用生成器来实现无限长的惰性列表,例如itertools.count()。您可以实现自己的def enumerate(iterable):zip(count(),iterable),或者使用while循环中的yield关键字来实现。
请注意:生成器实际上可以用于更多事情,例如实现协程或不确定性编程或其他优雅的事情。但是,我在这里提出的“惰性列表”观点是您会发现的最常见用法。
幕后花絮
这就是“ Python迭代协议”的工作方式。也就是说,执行list(makeRange(5))时发生了什么。这就是我之前所说的“懒惰的增量列表”。
>>> x = iter(范围(5))
>>>下一个(x)
>>>下一个(x)
1个
>>>下一个(x)
2
>>>下一个(x)
3
>>>下一个(x)
4
>>>下一个(x)
追溯(最近一次通话):
<模块>中第1行的文件“ ”
StopIteration
内置函数next()仅调用对象.next()函数,该函数是“迭代协议”的一部分,可以在所有迭代器上找到。您可以手动使用next()函数(以及迭代协议的其他部分)来实现奇特的事情,通常是以牺牲可读性为代价的,因此请避免这样做...
细节
通常,大多数人不会关心以下区别,并且可能想在这里停止阅读。
用Python讲,可迭代是“理解for循环的概念”的任何对象,例如列表[1,2,3],而迭代器是所请求的for循环的特定实例,例如[1,2,3] ,3] .__ iter __()。生成器与任何迭代器完全相同,但其编写方式(带有函数语法)除外。
当您从列表中请求迭代器时,它将创建一个新的迭代器。但是,当您从迭代器请求迭代器时(很少这样做),它只会为您提供自身的副本。
因此,在极少数情况下,您无法执行此类操作...
> x = myRange(5)
>清单(x)
[0,1,2,3,4]
>清单(x)
[]
...然后记住生成器是迭代器;也就是说,它是一次性的。如果要重用它,则应再次调用myRange(...)。如果需要两次使用结果,请将结果转换为列表并将其存储在变量x = list(myRange(5))中。那些绝对需要克隆生成器的人(例如,正在进行骇人的骇人的元编程的人)可以在绝对必要的情况下使用itertools.tee,因为可复制的迭代器Python PEP标准建议已被推迟。
|
yield关键字在Python中有什么作用?
答案大纲/摘要
具有yield的函数在调用时将返回Generator。
生成器是迭代器,因为它们实现了迭代器协议,因此您可以对其进行迭代。
也可以向生成器发送信息,使它在概念上成为协程。
在Python 3中,您可以使用yield from从两个方向将一个生成器委托给另一个生成器。
(附录对几个答案进行了评论,包括最上面的一个,并讨论了生成器中return的用法。)
发电机:
yield仅在函数定义内部是合法的,并且yield包含在函数定义中使其返回生成器。
生成器的想法来自具有不同实现方式的其他语言(请参见脚注1)。在Python的Generators中,代码的执行会在收益率点冻结。调用生成器时(下面讨论方法),恢复执行,然后冻结在下一个产量。
产量提供了
实现迭代器协议的简便方法,由以下两种方法定义:
__iter__和下一个(Python 2)或__next__(Python 3)。这两种方法
将对象设为可以与Iterator Abstract Base进行类型检查的迭代器
来自collections模块的类。
>>> def func():
...产生“我是”
...产生“发电机!”
...
>>> type(func)#具有yield的函数仍然是一个函数
<类型'功能'>
>>> gen = func()
>>> type(gen)#但它返回一个生成器
<类型'发电机'>
>>> hasattr(gen,'__iter__')#这是可迭代的
真正
>>> hasattr(gen,'next')#并带有.next(Python 3中的.__ next__)
True#实现迭代器协议。
生成器类型是迭代器的子类型:
>>>导入集合,类型
>>> issubclass(types.GeneratorType,collections.Iterator)
真正
并且如有必要,我们可以像这样进行类型检查:
>>> isinstance(gen,types.GeneratorType)
真正
>>> isinstance(gen,collections.Iterator)
真正
迭代器的一个功能是,一旦耗尽,就无法重用或重置它:
>>>列表(gen)
['我是,'发电机!']
>>>列表(gen)
[]
如果要再次使用其功能,则必须另做一个(请参见脚注2):
>>>列表(func())
['我是,'发电机!']
一个人可以通过编程方式产生数据,例如:
def func(an_iterable):
对于an_iterable中的项目:
产量项目
上面的简单生成器也等效于下面的生成器-从Python 3.3开始(在Python 2中不可用),您可以使用yield from:
def func(an_iterable):
来自an_iterable的收益
但是,收益来自还可以委派给子发电机,
这将在以下有关带有子协程的合作授权的部分中进行说明。
协程:
yield形成一个表达式,该表达式允许将数据发送到生成器中(请参见脚注3)
这是一个示例,请注意接收到的变量,该变量将指向发送到生成器的数据:
def bank_account(存款,利率):
而True:
计算的利息=利率*存入
收到=收益计算所得的利息
如果收到:
存入+ =已收到
>>> my_account = bank_account(1000,.05)
首先,我们必须使用内置函数将生成器排队。它会
根据版本的不同,调用相应的next或__next__方法
您正在使用的Python:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
现在我们可以将数据发送到生成器中。 (不发送是
与调用next相同。):
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
合作派遣到协程,收益来自
现在,回想一下yield from在Python 3中可用。这使我们可以将协程委托给子协程:
def money_manager(expected_rate):
#必须从.send()接收存款值:
under_management = yield#yield没有开始。
而True:
尝试:
另外的投资=预期收益率*管理不足
如果额外投资:
管理欠佳+ =额外投资
除了GeneratorExit:
'''待办事项:编写功能以将无人认领的资金汇入州''
提高
最后:
'''待办事项:编写功能以将税收信息邮寄给客户'''
def investment_account(已存款,经理):
'''非常简单的委托给经理的投资账户模型'''
#必须让管理员排队:
next(manager)#<-与manager.send相同(无)
#在这里我们将初始存款发送给经理:
manager.send(存款)
尝试:
经理收益
除了GeneratorExit:
返回manager.close()#委托?
现在我们可以将功能委托给子生成器,并且可以使用它
由上面的发电机产生:
my_manager = money_manager(.06)
my_account = investment_account(1000,my_manager)
first_year_return = next(my_account)#-> 60.0
现在模拟向该帐户再添加1,000,再加上该帐户的收益(60.0):
next_year_return = my_account.send(first_year_return + 1000)
next_year_return#123.6
您可以从PEP 380中了解有关yield的精确语义的更多信息。
其他方法:关闭并抛出
close方法在功能上提高GeneratorExit
执行被冻结。这也会被__del__调用,因此您
可以将任何清理代码放在您处理GeneratorExit的位置:
my_account.close()
您还可以引发可以在生成器中处理的异常
或传播回用户:
导入系统
尝试:
引发ValueError
除了:
my_manager.throw(* sys.exc_info())
筹款:
追溯(最近一次通话):
<模块>中第4行的文件“ ”
money_manager中的第6行的文件“ ”
<模块>中第2行的文件“ ”
ValueError
结论
我相信我已经涵盖了以下问题的各个方面:
yield关键字在Python中有什么作用?
事实证明,收益很大。我确定我可以添加更多
详尽的例子。如果您想要更多或有建设性的批评,请通过评论让我知道
下面。
附录:
对最佳/可接受答案的评论**
仅以列表为例,它对使可迭代的内容感到困惑。请参阅上面的参考资料,但总而言之:可迭代的对象具有返回迭代器的__iter__方法。迭代器提供.next(Python 2或.__ next __(Python 3))方法,该方法由for循环隐式调用,直到引发StopIteration,一旦这样做,它将继续这样做。
然后,它使用生成器表达式来描述什么是生成器。由于生成器只是创建迭代器的一种简便方法,因此它只会使问题感到困惑,而我们仍未进入屈服部分。
在控制发电机的排气中,他调用.next方法,而接下来应使用内置函数。这将是一个适当的间接层,因为他的代码在Python 3中不起作用。
Itertools?这根本与产量无关。
没有讨论yield的方法以及Python 3中新功能的产生。最高/可接受的答案是非常不完整的答案。
对生成器表达或理解中的屈服的答案的评论。
该语法当前允许列表理解中的任何表达式。
expr_stmt:testlist_star_expr(annassign | augassign(yield_expr | testlist)|
('='(yield_expr | testlist_star_expr))*)
...
yield_expr:“ yield” [yield_arg]
yield_arg:“来自”测试|测试清单
由于yield是一种表达方式,因此尽管没有特别好的用例,但人们仍认为将其用于理解或生成器表达很有趣。
CPython核心开发人员正在讨论弃用其津贴。
这是邮件列表中的相关帖子:
2017年1月30日19:05,布雷特·坎农写道:
2017年1月29日星期日,克雷格·罗德里格斯(Craig Rodrigues)在星期日写道:
两种方法我都可以。用Python 3保持现状
不好,恕我直言。
我的投票是SyntaxError,因为您没有从中得到期望
语法。
我同意这是我们最终的明智之地,因为任何代码
依靠当前的行为真的太聪明了
可维护的。
关于到达那里,我们可能想要:
3.7中的语法警告或弃用警告
2.7.x中的Py3k警告
3.8中的SyntaxError
干杯,尼克。
-Nick Coghlan | gmail.com上的ncoghlan |澳大利亚布里斯班
此外,还有一个悬而未决的问题(10544),似乎正说明这绝不是一个好主意(PyPy,用Python编写的Python实现,已经在发出语法警告。)
最重要的是,直到CPython的开发人员另行告诉我们为止:不要将yield放在生成器表达式或理解中。
生成器中的return语句
在Python 2中:
在生成器函数中,return语句不允许包含expression_list。在这种情况下,简单的返回指示生成器已完成,并将导致StopIteration升高。
expression_list基本上是由逗号分隔的任意数量的表达式-本质上,在Python 2中,您可以使用return停止生成器,但不能返回值。
在Python 3中:
在生成器函数中,return语句指示生成器已完成,并将引起StopIteration升高。返回的值(如果有)用作构造StopIteration的参数,并成为StopIteration.value属性。
脚注
提案中引用了CLU,Sather和Icon语言
向Python介绍生成器的概念。总体思路是
函数可以保持内部状态并产生中间值
用户按需提供数据点。这有望在性能上出众
其他方法,包括Python线程,在某些系统上甚至不可用。
例如,这意味着xrange对象(在Python 3中为range)不是迭代器,即使它们是可迭代的,因为它们可以被重用。像列表一样,它们的__iter__方法返回迭代器对象。
yield最初是​​作为声明引入的,这意味着它
只能出现在代码块中一行的开头。
现在yield将创建一个yield表达式。
https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt
提出此更改是为了允许用户将数据发送到生成器中,就像
一个人可能会收到它。要发送数据,必须能够将其分配给某物,并且
为此,声明将行不通。
|
yield就像return一样-它返回您告诉的任何内容(作为生成器)。不同之处在于,下次调用生成器时,执行从最后一次调用到yield语句开始。与return不同的是,在产生良率时不会清除堆栈帧,但是会将控制权转移回调用方,因此下次调用该函数时,其状态将恢复。
就您的代码而言,该函数get_child_candidates的行为就像一个迭代器,因此当您扩展列表时,它会一次向新列表添加一个元素。
list.extend调用迭代器,直到耗尽为止。在您发布的代码示例的情况下,只返回一个元组并将其附加到列表中会更加清楚。
|
还有另外一件事要提及:yield的函数实际上不必终止。我写了这样的代码:
def fib():
最后,cur = 0,1
而True:
屈服电流
最后,当前=当前,最后+当前
然后我可以在其他代码中使用它:
对于fib()中的f:
如果some_condition:中断
coolfuncs(f);
它确实有助于简化某些问题,并使某些事情更易于使用。
|
对于那些偏爱简单工作示例的人,请在此交互式Python会话中进行冥想:
>>> def f():
...产量1
...产量2
...产量3
...
>>> g = f()
>>>对于我在g中:
...打印(i)
...
1个
2
3
>>>对于我在g中:
...打印(i)
...
>>>#注意这次没有打印任何内容
|
TL; DR
代替这个:
def square_list(n):
the_list = []#替换
对于x在范围(n)中:
y = x * x
the_list.append(y)#这些
返回the_list#行
做这个:
def square_yield(n):
对于x在范围(n)中:
y = x * x
用这个生成y#。
每当您发现自己从头开始建立清单时,请改为提供每份清单。
这是我第一次屈服。
产量是一种含糖的说法
建立一系列的东西
相同的行为:
>>>对于square_list(4)中的square:
...打印(正方形)
...
1个
4
9
>>>对于square_yield(4)中的平方:
...打印(正方形)
...
1个
4
9
不同的行为:
收益是单次通过:您只能迭代一次。当函数具有收益时,我们称其为生成器函数。而迭代器就是它返回的内容。这些术语在揭示。我们失去了容器的便利性,但获得了按需计算且任意长的序列的功效。
产量是懒惰的,它推迟了计算。当调用它时,其中包含yield的函数实际上根本不会执行。它返回一个迭代器对象,该对象记住它从何处中断。每次您在迭代器上调用next()(这在for循环中发生)时,执行都会向前推进到下一个收益。 return引发StopIteration并结束序列(这是for循环的自然结束)。
产量多才多艺。数据不必全部存储在一起,可以一次存储一次。它可以是无限的。
>>> def squares_all_of_them():
... x = 0
...而True:
...产量x * x
... x + = 1
...
>>>正方形= squares_all_of_them()
>>>范围内的_:(4):
...打印(下一个(正方形))
...
1个
4
9
如果您需要多次通过并且序列不太长,只需在其上调用list()即可:
>>>列表(square_yield(4))
[0,1,4,9]
明智地选择yield,因为两种含义都适用:
产量—生产或提供(如在农业中)
...提供系列中的下一个数据。
屈服—让步或放弃(如在政治权力中一样)
...放弃CPU执行,直到迭代器前进。
|
收益为您提供发电机。
def get_odd_numbers(i):
返回范围(1,i,2)
def yield_odd_numbers(i):
对于x在范围(1,i,2)中:
产量x
foo = get_odd_numbers(10)
条= yield_odd_numbers(10)
富
[1、3、5、7、9]
酒吧
<生成器对象yield_odd_numbers at 0x1029c6f50>
bar.next()
1个
bar.next()
3
bar.next()
5
如您所见,在第一种情况下,foo一次将整个列表保存在内存中。包含5个元素的列表并不是什么大问题,但是如果您想要500万个列表,该怎么办?这不仅是一个巨大的内存消耗者,而且在调用函数时还花费大量时间来构建。
在第二种情况下,bar只是为您提供了一个生成器。生成器是可迭代的-这意味着您可以在for循环等中使用它,但是每个值只能被访问一次。所有的值也不会同时存储在存储器中。生成器对象“记住”您上次调用它时在循环中的位置-这样,如果您使用的是一个迭代的(例如)计数为500亿,则不必计数为500亿立即存储500亿个数字以进行计算。
再次,这是一个非常人为的示例,如果您真的想计数到500亿,则可能会使用itertools。 :)
这是生成器最简单的用例。如您所说,它可以用来编写有效的排列,使用yield可以将内容推入调用堆栈,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历以及所有其他方式。
|
它正在返回发电机。我对Python并不是特别熟悉,但是我相信,如果您熟悉Python,它与C#的迭代器块是一样的东西。
关键思想是,编译器/解释器/会做一些棘手的事情,以便就调用者而言,他们可以继续调用next(),它将继续返回值-就像Generator方法已暂停一样。现在显然您不能真正地“暂停”方法,因此编译器构建了一个状态机,供您记住您当前所​​在的位置以及局部变量等的外观。这比自己编写迭代器要容易得多。
|
在描述如何使用生成器的许多很棒的答案中,我还没有给出一种答案。这是编程语言理论的答案:
Python中的yield语句返回一个生成器。 Python中的生成器是一个返回延续的函数(特别是协程的一种,但是延续代表了一种更通用的机制来了解正在发生的事情)。
编程语言理论中的延续是一种更为基础的计算,但是由于它们很难推理而且也很难实现,因此并不经常使用。但是,关于延续是什么的想法很简单:只是尚未完成的计算状态。在此状态下,将保存变量的当前值,尚未执行的操作等。然后,在稍后的某个时刻,可以在程序中调用继续,以便将程序的变量重置为该状态,并执行保存的操作。
以这种更一般的形式进行的连续可以两种方式实现。以call / cc的方式,将程序的堆栈按字面意义保存,然后在调用延续时,将还原堆栈。
在延续传递样式(CPS)中,延续只是普通的函数(仅在函数是第一类的语言中),程序员明确地对其进行管理并传递给子例程。以这种方式,程序状态由闭包(以及恰好在其中编码的变量)表示,而不是由位于堆栈中某个位置的变量表示。管理控制流的函数接受连续作为参数(在CPS的某些变体中,函数可以接受多个连续),并通过简单地调用它们并随后返回来调用它们来操纵控制流。延续传递样式的一个非常简单的示例如下:
def save_file(文件名):
def write_file_continuation():
write_stuff_to_file(文件名)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
在这个(非常简单的)示例中,程序员保存了将文件实际写入连续的操作(该操作可能是非常复杂的操作,需要写出许多细节),然后传递该连续(例如,首先类关闭)到另一个进行更多处理的运算符,然后在必要时调用它。 (我在实际的GUI编程中经常使用此设计模式,这是因为它节省了我的代码行,或更重要的是,在GUI事件触发后管理控制流。)
在不失一般性的前提下,本文的其余部分将连续性概念化为CPS,因为它很容易理解和阅读。
现在让我们谈谈Python中的生成器。生成器是延续的特定子类型。延续通常能够保存计算状态(即程序的调用堆栈),而生成器只能保存迭代器上的迭代状态。虽然,对于发电机的某些使用情况,此定义会产生误导。例如:
def f():
而True:
产量4
显然,这是一个合理的迭代器,其行为已得到很好的定义-每次生成器对其进行迭代时,它都会返回4(并且永远如此)。但是在考虑迭代器时(例如,集合中的x:do_something(x)),可能不会想到原型的可迭代类型。此示例说明了生成器的功能:如果迭代器是任何东西,则生成器可以保存其迭代状态。
重申一下:连续可以保存程序堆栈的状态,而生成器可以保存迭代的状态。这意味着延续比生成器强大得多,但是生成器也非常简单。它们对于语言设计者来说更容易实现,对程序员来说也更容易使用(如果您有时间要燃烧,请尝试阅读并理解有关延续和call / cc的本页)。
但是您可以轻松地将生成器实现(并将其概念化)为连续传递样式的一种简单的特定情况:
每当调用yield时,它都会告诉函数返回一个延续。再次调用该函数时,将从中断处开始。因此,在伪伪代码(即不是伪代码,而不是代码)中,生成器的next方法基本上如下:
Generator()类:
def __init __(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(可迭代)
def next(self):
值,next_continuation =self.next_continuation()
self.next_continuation = next_continuation
返回值
其中yield关键字实际上是实际生成器函数的语法糖,基本上是这样的:
def generatorfun(可迭代):
如果len(iterable)== 0:
提高StopIteration
其他:
返回(iterable [0],lambda:generatorfun(iterable [1:]))
请记住,这只是伪代码,Python中生成器的实际实现更为复杂。但是,作为练习以了解发生了什么,请尝试使用连续传递样式来实现生成器对象,而不使用yield关键字。
|
这是简单语言的示例。我将提供高级人类概念与低级Python概念之间的对应关系。
我想对数字序列进行运算,但是我不想为创建该序列而烦恼自己,我只想着重于自己想做的运算。因此,我执行以下操作:
我打电话给你,告诉你我想要一个以特定方式产生的数字序列,让您知道算法是什么。此步骤对应于定义生成器函数,即包含yield的函数。
稍后,我告诉您,“好,准备告诉我数字的顺序”。此步骤对应于调用生成器函数,该函数返回生成器对象。请注意,您还没有告诉我任何数字。你只要拿起纸和铅笔。
我问你,“告诉我下一个号码”,然后你告诉我第一个号码;之后,您等我问您下一个电话号码。记住您的位置,已经说过的电话号码以及下一个电话号码是您的工作。我不在乎细节。此步骤对应于在生成器对象上调用.next()。
…重复上一步,直到…
最终,您可能会走到尽头。你不告诉我电话号码;您只是大声喊道:“抱马!我做完了!没有数字了!”此步骤对应于生成器对象结束其工作,并引发StopIteration异常。生成器函数不需要引发异常。函数结束或发出返回值时,它将自动引发。
这就是生成器的功能(包含yield的函数);它开始执行,在产生任何结果时暂停,并在询问.next()值时从其最后一点继续。根据设计,它与Python的迭代器协议完美契合,该协议描述了如何顺序请求值。
迭代器协议最著名的用户是Python中的for命令。因此,只要您执行以下操作:
对于顺序项目:
序列是如上所述的列表,字符串,字典还是生成器对象都没有关系;结果是一样的:您从一个序列中逐个读取项目。
请注意,定义一个包含yield关键字的函数并不是创建生成器的唯一方法。这是创建一个的最简单的方法。
有关更准确的信息,请阅读Python文档中有关迭代器类型,yield语句和生成器的信息。
|
尽管很多答案表明了为什么要使用yield来创建生成器,但是yield有更多用途。创建协程非常容易,这使信息可以在两个代码块之间传递。我不会重复任何关于使用yield来创建生成器的优秀示例。
为了帮助理解以下代码中的yield,您可以用手指通过任何具有yield的代码来跟踪循环。每次您的手指触碰良率时,您都必须等待下一个或要发送的输入。调用next时,您将遍历代码,直到达到收益为止。收益率右侧的代码将被评估并返回给调用方……然后等待。再次调用next时,您将通过代码执行另一个循环。但是,您会注意到,在协程中,yield也可以与send一起使用……它将从调用方将值发送到yield函数。如果给出了发送,则yield接收发送的值,然后将其吐出左侧。然后,通过代码进行的跟踪将一直进行到您再次达到yield为止(最后返回值,就像调用了next一样)。
例如:
>>> def coroutine():
...我= -1
...而True:
...我+ = 1
... val =(收益i)
... print(“收到的%s”%val)
...
>>>序列= coroutine()
>>> sequence.next()
>>> sequence.next()
没有收到
1个
>>> sequence.send('hello')
收到你好
2
>>> sequence.close()
|
还有另一个yield用法和含义(自Python 3.3起):
的收益
从PEP 380-委托给子生成器的语法:
提出了一种语法,供生成器将其部分操作委托给另一生成器。这允许包含“ yield”的一段代码被分解并放置在另一个生成器中。此外,允许子生成器返回一个值,并且该值可供委托生成器使用。
当一个生成器重新产生另一个生成器的值时,新语法还为优化提供了一些机会。
此外,这将介绍(自Python 3.5起):
异步def new_coroutine(数据):
...
等待blocking_action()
为了避免将协程与常规生成器混淆(两者目前都使用了产量)。
|
所有好的答案,但是对于新手来说有点困难。
我认为您已经了解了return语句。
打个比方,收益率和收益率是双胞胎。 return表示“返回并停止”,而“ yield”表示“返回但继续”
尝试获取带有返回的num_list。
def num_list(n):
对于我在范围(n)中:
还给我
运行:
在[5]中:num_list(3)
出[5]:0
看,您只会得到一个数字,而不是列表。 return永远不会让你高高兴兴,只执行一次就退出。
有产量
用收益替换收益:
在[10]中:def num_list(n):
...:对于范围(n)中的i:
...:让我
...:
在[11]中:num_list(3)
Out [11]:<位于0x10327c990的发电机对象num_list>
在[12]中:list(num_list(3))
出[12]:[0、1、2]
现在,您将赢得所有数字。
比较一次运行并停止的收益率,收益运行时间是您计划的时间。
您可以将return解释为其中之一,并把yield解释为全部。这称为可迭代。
我们可以再执行一步,用return重写yield语句
在[15]中:def num_list(n):
...:结果= []
...:对于范围(n)中的i:
...:result.append(i)
...:返回结果
在[16]中:num_list(3)
出[16]:[0、1、2]
这是产量的核心。
列表返回输出和对象收益输出之间的区别是:
您将始终从列表对象获取[0,1,2],但只能从“对象产量输出”中检索一次。因此,它具有一个新的名称生成器对象,如Out [11]中所示:<生成器对象num_list位于0x10327c990>。
总之,作为一个隐喻,它可以:
收益率和收益率是双胞胎
列表和发电机是双胞胎
|
从编程的角度来看,迭代器被实现为thunk。
为了将迭代器,生成器和线程池实现为thunk,以便并发执行等,人们使用发送到具有分派器的闭包对象的消息,然后分派器对“消息”做出响应。
“下一个”是发送到闭包的消息,由“ iter”调用创建。
有很多方法可以实现这种计算。我使用了变异,但是可以通过返回当前值和下一个生成器(使其参照透明)来进行这种无变异的计算。 Racket使用某些中间语言对初始程序进行一系列转换,这种重写之一使yield运算符可以使用更简单的运算符转换为某种语言。
这是使用R6RS的结构如何重写yield的演示,但其语义与Python相同。它是相同的计算模型,只需要更改语法即可使用Python的产量重写它。
欢迎使用Racket v6.5.0.3。
->(定义gen
(lambda(l)
(定义产量
(lambda()
(如果(为空?l)
'结束
(让[[v [car l)))
(设置!l(cdr l))
v))))
(λ(米)
(案例m
('收益率(yield))
('init(lambda(数据)
(设置!l个数据)
'好))))))
->(定义流(gen'(1 2 3)))
->(流的收益)
1个
->(流的收益)
2
->(流的收益)
3
->(流的收益)
'结束
->((流'init)'(a b))
'好
->(流的收益)
'一种
->(流的收益)
'b
->(流的收益)
'结束
->(流的收益)
'结束
->
|
以下是一些Python示例,这些示例说明如何实际实现生成器,就像Python没有为其提供语法糖一样:
作为Python生成器:
从itertools导入islice
def fib_gen():
a,b = 1,1
而True:
产生一个
a,b = b,a + b
断言[1,1,2,3,5] == list(islice(fib_gen(),5))
使用词法闭包而不是生成器
def ftake(fnext,最后):
返回[在xrange(last)中_的[fnext())]
def fib_gen2():
#funky作用域由于python2.x解决方法
#对于python 3.x使用非本地
def _():
_.a,_。b = _.b,_。a + _.b
返回_.a
_.a,_。b = 0、1
返回_
断言[1,1,2,3,5] == ftake(fib_gen2(),5)
使用对象闭包而不是生成器(因为ClosuresAndObjectsAreEquivalent)
类fib_gen3:
def __init __():
self.a,self.b = 1,1
def __call __(自己):
r =自我
self.a,self.b = self.b,self.a + self.b
返回r
断言[1,1,2,3,5] == ftake(fib_gen3(),5)
|
我本打算发布“阅读Beazley的“ Python:基本参考”的第19页,以快速了解生成器”,但是已经有许多其他人发布了不错的描述。
另外,请注意,协程可以将yield用作生成函数的双重用途。尽管(yield)与代码段用法不同,但它可以用作函数中的表达式。当调用者使用send()方法向该方法发送值时,协程将一直执行,直到遇到下一个(yield)语句为止。
发电机和协程是设置数据流类型应用程序的一种很酷的方法。我认为值得了解函数中yield语句的其他用法。
|
这是一个简单的示例:
def isPrimeNumber(n):
打印“ isPrimeNumber({})调用”。format(n)
如果n == 1:
返回False
对于范围(2,n)中的x:
如果n%x == 0:
返回False
返回True
def素数(n = 1):
while(真):
打印“循环步骤---------------- {}”。format(n)
如果isPrimeNumber(n):产生n
n + = 1
对于质数()中的n:
如果n> 10:break
打印“写结果{}”。format(n)
输出:
循环步骤---------------- 1
isPrimeNumber(1)调用
循环步骤---------------- 2
isPrimeNumber(2)调用
循环步骤---------------- 3
isPrimeNumber(3)调用
写结果3
循环步骤---------------- 4
isPrimeNumber(4)调用
循环步骤---------------- 5
isPrimeNumber(5)调用
写结果5
循环步骤---------------- 6
isPrimeNumber(6)调用
循环步骤---------------- 7
isPrimeNumber(7)调用
写结果7
循环步骤---------------- 8
isPrimeNumber(8)调用
循环步骤---------------- 9
isPrimeNumber(9)调用
循环步骤---------------- 10
isPrimeNumber(10)调用
循环步骤---------------- 11
isPrimeNumber(11)调用
我不是Python开发人员,但在我看来,yield保持程序流的位置,而下一个循环从“ yield”位置开始。似乎它在那个位置等待,就在那之前,在外面返回一个值,下一次继续工作。
这似乎是一种有趣而又不错的能力:D
|
这是产量的心理印象。
我喜欢将线程视为具有堆栈(即使未以这种方式实现)。
调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回。再也看不到其局部变量的值。
使用yield函数,当其代码开始运行时(即在调用函数后,返回生成器对象,然后调用next()方法的生成器对象),它类似地将其局部变量放到堆栈上并进行一段时间的计算。但是,当它命中yield语句时,在清除堆栈的一部分并返回之前,它会对其局部变量进行快照并将其存储在生成器对象中。它还会在代码中写下当前位置(即特定的yield语句)。
因此,这是生成器挂起的一种冻结函数。
随后调用next()时,它将函数的所有物检索到堆栈上并对其进行动画处理。该函数从中断处继续进行计算,而忽略了它刚刚在冷藏库中度过了一个永恒的事实。
比较以下示例:
def normalFunction():
返回
如果为假:
通过
def yielderFunction():
返回
如果为假:
产量12
当我们调用第二个函数时,它的行为与第一个函数非常不同。 yield语句可能无法到达,但是如果它存在于任何地方,它将改变我们正在处理的内容的性质。
>>> yielderFunction()
<生成器对象yielderFunction at 0x07742D28>
调用yielderFunction()不会运行其代码,但是会使代码生成一个生成器。 (为便于阅读,使用yielder前缀命名此类名称可能是个好主意。)
>>> gen = yielderFunction()
>>> dir(gen)
['__类__',
...
'__iter __',#返回gen本身,以使其与容器统一工作
...#设置为for循环时。 (容器返回一个迭代器。)
'关',
'gi_code',
'gi_frame',
'gi_running',
'next',#运行函数主体的方法。
'发送',
'扔']
gi_code和gi_frame字段是冻结状态的存储位置。用dir(..)探索它们,我们可以确认我们上面的心理模型是可信的。
|
一个简单的例子来了解它是什么:yield
def f123():
对于_范围(4):
产量1
产量2
对于我在f123()中:
打印(i)
输出为:
1 2 1 2 1 2 1 2
|
就像每个答案所暗示的那样,yield用于创建序列生成器。它用于动态生成一些序列。例如,在网络上逐行读取文件时,可以按以下方式使用yield函数:
def getNextLines():
而con.isOpen():
产生con.read()
您可以在代码中使用它,如下所示:
对于getNextLines()中的行:
doSomeThing(行)
执行控制转移陷阱
执行yield时,执行控制将从getNextLines()转移到for循环。因此,每次调用getNextLines()时,都会从上次暂停的位置开始执行。
因此,简而言之,具有以下代码的函数
def simpleYield():
产生“第一次”
产生“第二次”
产生“第三次”
产生“现在有一些有用的值{}”。format(12)
对于我在simpleYield()中:
打印我
将打印
“第一次”
“第二次”
“第三次”
“现在有一些有用的值12”
|
(我下面的回答仅从使用Python生成器的角度讲,而不是生成器的基础实现机制,其中涉及一些堆栈和堆操作的技巧。)
当使用yield而不是python函数中的返回值时,该函数将变成一种特殊的生成器函数。该函数将返回生成器类型的对象。 yield关键字是一个标志,用于通知python编译器专门对待此类功能。普通函数将在返回一些值后终止。但是在编译器的帮助下,可以将generator函数视为可恢复的。也就是说,将恢复执行上下文,并且将从上次运行继续执行。在显式调用return之前,它将引发StopIteration异常(这也是迭代器协议的一部分),或者到达函数的结尾。我发现了很多有关生成器的参考,但是从功能编程的角度来看,这是最易理解的。
(现在,我想根据我自己的理解来讨论generator的基本原理,以及迭代器。我希望这可以帮助您掌握迭代器和generator的基本动机。这种概念在其他语言(例如C#)中也会出现。)
据我了解,当我们要处理一堆数据时,通常会先将数据存储在某个地方,然后再逐一处理。但是这种幼稚的方法是有问题的。如果数据量巨大,则预先存储它们是很昂贵的。因此,为什么不间接存储某种类型的元数据,而不是直接存储数据本身,即逻辑如何计算数据。
有两种包装此类元数据的方法。
面向对象的方法,我们将元数据包装为一个类。这就是实现迭代器协议的所谓迭代器(即__next __()和__iter __()方法)。这也是常见的迭代器设计模式。
在功能方法上,我们将元数据包装为一个函数。这是
所谓的发电机功能。但实际上,返回的生成器对象仍然是IS-A迭代器,因为它也实现了迭代器协议。
无论哪种方式,都会创建一个迭代器,即某个可以为您提供所需数据的对象。 OO方法可能有点复杂。无论如何,使用哪个取决于您。
|
总而言之,yield语句将您的函数转换为一个工厂,该工厂产生一个称为生成器的特殊对象,该对象环绕原始函数的主体。迭代生成器后,它将执行您的函数,直到达到下一个yield为止,然后挂起执行并评估传递给yield的值。它将在每次迭代中重复此过程,直到执行路径退出函数为止。例如,
def simple_generator():
产生一个
产生“两个”
产生“三”
对于我在simple_generator()中:
打印我
简单地输出
一
二
三
动力来自将生成器与一个计算序列的循环配合使用,生成器每次执行循环都会停止,以“屈服”下一个计算结果,通过这种方式,它可以即时计算列表,而好处是可以存储保存用于特别大的计算
假设您要创建自己的范围函数以产生可迭代的数字范围,则可以这样做,
def myRangeNaive(i):
n = 0
范围= []
而n >> fib()
<生成器对象fib位于0x7fa38394e3b8>
这是因为yield的存在向Python发出信号,要求您创建一个生成器,即一个按需生成值的对象。
那么,如何生成这些值?可以直接使用下一步的内置函数来完成此操作,也可以通过将其提供给使用值的构造来间接完成此操作。
使用内置的next()函数,您可以直接调用.next / __ next__,强制生成器生成一个值:
>>> g = fib()
>>>下一个(g)
1个
>>>下一个(g)
1个
>>>下一个(g)
2
>>>下一个(g)
3
>>>下一个(g)
5
间接地,如果将fib提供给for循环,列表初始值设定项,元组初始值设定项或其他任何期望对象产生/产生值的对象,则将“消耗”产生器,直到不再产生任何值为止(它返回):
结果= []
对于我在fib(30)中:#消耗fib
results.append(i)
#也可以用
结果= list(fib(30))#消耗fib
同样,使用元组初始化程序:
>>>元组(fib(5))#消耗fib
(1、2、3、5)
生成器在延迟方面与功能有所不同。它通过保持其本地状态并允许您在需要时恢复来实现此目的。
首次调用fib时:
f = fib()
Python编译函数,遇到yield关键字,然后简单地将生成器对象返回给您。看起来不是很有帮助。
然后,当您请求它直接或间接生成第一个值时,它将执行找到的所有语句,直到遇到yield为止,然后将您提供的值返回给yield并暂停。为了更好地说明这一点,让我们使用一些打印调用(如果在Python 2上,用打印“文本”代替):
def yielder(值):
“”“这是一个无限生成器。只能在其上使用next
而1:
打印(“我将为您创造价值”)
print(“然后我会停一会儿”)
产量值
打印(“让我们再次遍历它。”)
现在,输入REPL:
>>> gen = yielder(“ Hello,yield!”)
您现在有了一个生成器对象,等待它生成值的命令。接下来使用并查看打印出来的内容:
>>> next(gen)#运行直到找到产量
我将为您创造价值
那我停一会儿
“你好,屈服!”
未报价的结果是所打印的内容。引用的结果是从yield返回的结果。现在再次拨打电话:
>>> next(gen)#从产量继续运行并再次运行
让我们再来一次。
我将为您创造价值
那我停一会儿
“你好,屈服!”
生成器记住它已停顿在yield值上,然后从那里恢复。打印下一条消息,并再次执行搜索yield语句以使其暂停的消息(由于while循环)。
|
1个
2
下一个
高度活跃的问题。赢得10个声誉才能回答这个问题。信誉要求有助于保护该问题免受垃圾邮件和非答复活动的侵害。
不是您要找的答案?浏览其他问题标记为python的迭代器生成器产生协程或询问您自己的问题。